package com.alks.common.aspect;

import com.alks.common.aop.RedisLock;
import com.alks.common.service.RedisService;
import com.alks.entity.exception.LockException;
import com.alks.entity.exception.ServiceErrorException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: chen
 * @date: 2024/12/5
 * @description: 分布式锁切面实现
 *      调用方式 @RedisLock(key = "key:lock", expireTime = 10)
 */
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Resource
    private RedisService redisService;

    private final ExpressionParser parser = new SpelExpressionParser();

    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        String rawKey = redisLock.key();
        int expireTime = redisLock.expireTime();

        // 动态解析注解中的 key
        String key = parseKey(rawKey, joinPoint);

        // 唯一标识，确保锁是当前线程持有的
        String value = UUID.randomUUID().toString();

        try {
            // 尝试获取锁
            boolean locked = tryLock(key, value, expireTime, TimeUnit.SECONDS);
            if (!locked) {
                // 获取方法名称
                Signature signature = joinPoint.getSignature();
                String methodName = "";
                if (signature instanceof MethodSignature) {
                    MethodSignature methodSignature = (MethodSignature) signature;
                    methodName = methodSignature.getMethod().getName();
                }
                log.info("获取分布式锁失败 {}.{}", joinPoint.getTarget().getClass().getName(), methodName);
                throw new LockException(redisLock.message());
            }
            // 启动锁续期任务
            startLockRenewTask(key, value, expireTime);
            // 执行业务逻辑
            return joinPoint.proceed();
        } finally {
            // 释放锁
            unlock(key, value);
        }
    }

    /**
     * 锁续期逻辑
     */
    private void startLockRenewTask(String lockKey, String lockValue, long expireTime) {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(expireTime * 1000 / 2); // 每次续期在过期时间的一半
                    if (existLock(lockKey, lockValue) && renewalLock(lockKey, expireTime, TimeUnit.SECONDS)) {
                        log.info("锁续期成功：{}", lockKey);
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }).start();
    }

    /**
     * 尝试获取锁
     */
    private boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {
        Boolean success = redisService.setIfAbsent(key, value, expireTime, timeUnit);
        return success != null && success;
    }

    /**
     * 释放锁
     */
    private void unlock(String key, Object value) {
        String currentValue = redisService.get(key);
        if (value.equals(currentValue)) {
            redisService.delete(key);
        }
    }

    /**
     * 是否存在锁
     */
    private boolean existLock(String key, Object value) {
        String currentValue = redisService.get(key);
        return value.equals(currentValue);
    }

    /**
     * 锁续期
     */
    private boolean renewalLock(String key, long expireTime, TimeUnit timeUnit) {
        return redisService.setExpireTime(key, expireTime, timeUnit);
    }

    /**
     * 解析 SpEL 表达式动态获取 key
     */
    private String parseKey(String rawKey, ProceedingJoinPoint joinPoint) {
        if (rawKey == null || rawKey.trim().isEmpty()) {
            throw new IllegalArgumentException("RedisLock key 不能为空");
        }

        if (!rawKey.contains("#")) {
            return rawKey;
        }

        // 获取方法签名和参数
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames(); // 方法参数名
        Object[] args = joinPoint.getArgs(); // 方法参数值

        // 创建 SpEL 上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]); // 参数名和值绑定
        }

        try {
            // 解析 SpEL 表达式
            String key = parser.parseExpression(rawKey).getValue(context, String.class);
            if (key == null || key.trim().isEmpty()) {
                throw new IllegalArgumentException("解析 RedisLock key 失败，结果为空");
            }
            return key;
        } catch (Exception e) {
            throw new IllegalArgumentException("解析 RedisLock key 失败: " + rawKey, e);
        }
    }
}
